#ifndef _ACTOR_H_
#define _ACTOR_H_

#include <SECore/Ecs/Component.h>
#include <memory>
#include <unordered_map>
#include <vector>
#include <optional>
#include <SECore/Signal.h>
#include <SECore/Serializable.h>
#include <SECore/SignalManager.h>

namespace SEngine
{
    class Scene;
    class Actor : public std::enable_shared_from_this<Actor>, public SignalManager
    {
    public:
        ENABLE_SERIALIZE(Actor)
    public:
        Actor(Scene & scene);
        virtual ~Actor();

        void SetParent(Actor & parent);
        std::optional<Actor *> GetParent() { return m_parent; }
        void AddChild(Actor & child);
        void RemoveChild(Actor & child);
        void RemoveParent();
        void ClearChildren();
        bool HasChildren() { return !m_children.empty(); }

        template<typename T, typename... Args>
        T & AddComponent(Args&&... args);
        template<typename T>
        void RemoveComponent();
        template<typename T>
        std::optional<std::reference_wrapper<T>> GetComponent();
        template<typename T>
        bool HasComponent() { return GetComponent<T>().has_value(); }

        void BeforeUpdate();
        void OnUpdate(int delta);
        void AfterUpdate();

        void ForeachChildren(std::function<void(Actor &)>&& fn);
        void ClearComponents();

        Scene & GetScene() { return m_scene; }

    public:
        Signal<IComponent&> AddComponentSignal;
        Signal<IComponent&> RemoveComponentSignal;

    private:
        Scene & m_scene;
        std::unordered_map<size_t, std::unique_ptr<IComponent>> m_components;
        std::optional<Actor *> m_parent;
        std::vector<std::weak_ptr<Actor>> m_children;
    };
    
    template<typename T, typename... Args>
    inline T & Actor::AddComponent(Args&&... args)
    {
        auto id = typeid(T::CompType).hash_code();
        if (m_components.find(id) == m_components.end()) {
            m_components.emplace(id, std::make_unique<T>(*this, std::forward<Args>(args)...));
            m_components.at(id)->Attach();
            T & comp = dynamic_cast<T&>(*m_components.at(id));
            AddComponentSignal.Invoke(comp);
        }
        T & comp = dynamic_cast<T&>(*m_components.at(id));
        return comp;
    }

    template <typename T>
    inline void Actor::RemoveComponent()
    {
        auto id = typeid(T::CompType).hash_code();
        if (m_components.find(id) != m_components.end()) {
            auto & comp = *m_components.at(id);
            RemoveComponentSignal.Invoke(comp);
            m_components.at(id)->Detach();
            m_components.erase(id);
        }
    }

    template <typename T>
    inline std::optional<std::reference_wrapper<T>> Actor::GetComponent()
    {
        auto id = typeid(T::CompType).hash_code();
        if (m_components.find(id) == m_components.end()) {
            return std::nullopt;
        }
        return std::ref(dynamic_cast<T&>(*m_components.at(id)));
    }
} // namespace SEngine

#endif